RAGFlow代理组件的核心
- 集成外部数据源:通过检索模块来继承外部知识库、数据库、文档等内容,增强模型的回答能力
- 生成式任务处理:利用模型生成的能力,基于用户的查询返回自然语言的回答
- 模块化:RAGFlow支持灵活的模块化设计,可以通过配置和集成不同的组件来满足特定的需求
RAGFlow代理组件
RAGFlow 的代理组件是其架构中的核心部分,负责管理与外部数据源和生成模型的交互。代理组件不仅可以从不同的外部数据源中获取知识,还能将检索到的信息与生成模型结合,从而生成更加准确的回答。
代理组件的功能
- 数据检索:代理组件会从多个外部数据源(如数据库、文档、API等)进行数据检索,获取与用户查询相关的信息。
- 生成模型的调用:将检索到的信息与生成模型结合,通过生成模型提供一个流畅且准确的答案。
- 多源数据融合:在生成回答时,代理组件能够处理多来源的数据,并将其融合成最终的答案。
组件 → web/src/utils *待定
组件前端交互中心:web/src/utils/api.ts
它定义了一些与“画布”相关的 API 接口地址,用于与后端进行通信。通过这些接口,前端应用可以进行不同操作,如获取模板、操作画布、调试等
listTemplates: 用于获取所有画布模板的列表,发送请求到
/canvas/templates
。
listCanvas: 获取画布列表的接口,发送请求到/canvas/list
Begin组件
Begin 组件设置开场问候语或接受用户的输入。当您创建代理时,它会自动填充到画布上,无论是从模板还是从头开始(从空白模板)。工作流程中应该只有一个 Begin 组件。
api/ragflow/web/src/locales/zh.ts
……
setAnOpenerInitial:你好! 我是你的助理,有什么可以帮到你的吗?
,
静态消息: /v1/canvas/set
生成问答、问题优化、问题类型、关键词:/v1/llm/list
知识检索:/v1/llm/list
+ /v1/kb/list
以**问题优化 **(RewriteQuestion:MoodyCrabsPeel) 为例:
后端内容
知识检索:Retrieval
路径→agent/component/retrieval.py
# 记录日志
import logging
# 抽象基类模块
from abc import ABC
# 用于数据处理,尤其是处理数据框(DataFrame)
import pandas as pd
# 从数据库相关模块导入 LLMType,这个可能是用来定义 LLM(语言模型)类型的枚举
from api.db import LLMType
# 从数据库服务模块中导入,分别用于知识库服务和 LLM 配置的封装
from api.db.services.knowledgebase_service import KnowledgebaseService
from api.db.services.llm_service import LLMBundle
# 加载应用程序的设置
from api import settings
# 基础组件类,用于构建此组件的父类
from agent.component.base import ComponentBase, ComponentParamBase
# 从标签模块中导入,用于对查询进行标记
from rag.app.tag import label_question
class RetrievalParam(ComponentParamBase):
"""
Define the Retrieval component parameters.
"""
def __init__(self):
super().__init__()
# 相似度阈值
self.similarity_threshold = 0.2
# 关键字相似度的权重
self.keywords_similarity_weight = 0.5
# 检索时返回的结果数
self.top_n = 8
# 在检索时考虑的最大数量
self.top_k = 1024
# 知识库的 ID 列表
self.kb_ids = []
# 重新排序模型的 ID (可能为空)
self.rerank_id = ""
# 当没有找到任何结果时的响应文本
self.empty_response = ""
#### check_decimal_float???
# check():对参数进行验证,确保相似度阈值和权重为有效的小数,且 top_n 是正数。
def check(self):
# "相似性阈值"
self.check_decimal_float(self.similarity_threshold, "[Retrieval] Similarity threshold")
# "关键词相似度权重"
self.check_decimal_float(self.keywords_similarity_weight, "[Retrieval] Keyword similarity weight")
self.check_positive_number(self.top_n, "[Retrieval] Top N")
# Retrieval 类继承自 ComponentBase 和 ABC(抽象基类)
# 表示这是一个组件,并实现了抽象方法
class Retrieval(ComponentBase, ABC):
component_name = "Retrieval"
##### history, **kwargs???
# _run为核心方法
# query输入查询内容 包括'content'键 提取第一个内容项 将查询内容转换为字符串
def _run(self, history, **kwargs):
query = self.get_input()
query = str(query["content"][0]) if "content" in query else ""
# 从知识库服务中获取指定 ID 的知识库。如果没有找到相关的知识库,返回空的响应。
kbs = KnowledgebaseService.get_by_ids(self._param.kb_ids)
if not kbs:
return Retrieval.be_output("")
# 获取所有知识库的嵌入模型 ID(embd_id),确保所有知识库使用相同的嵌入模型。
# 如果使用的嵌入模型不一致,抛出异常
embd_nms = list(set([kb.embd_id for kb in kbs]))
assert len(embd_nms) == 1, "Knowledge bases use different embedding models."
##### LLMBundle???
# 创建 LLMBundle 实例来封装嵌入模型,使用画布的租户 ID 和嵌入模型的 ID 配置嵌入模型,并设置到画布
embd_mdl = LLMBundle(self._canvas.get_tenant_id(), LLMType.EMBEDDING, embd_nms[0])
self._canvas.set_embedding_model(embd_nms[0])
# 若提供了 rerank_id,则加载重新排序的模型
rerank_mdl = None
if self._param.rerank_id:
rerank_mdl = LLMBundle(kbs[0].tenant_id, LLMType.RERANK, self._param.rerank_id)
# 使用 settings.retrievaler.retrieval 方法执行知识检索操作,传入查询和各种配置参数
# query:用户查询 embd_mdl:嵌入模型 其他参数包括知识库 ID、相似度阈值、关键词相似度权重等
kbinfos = settings.retrievaler.retrieval(query, embd_mdl, kbs[0].tenant_id, self._param.kb_ids,
1, self._param.top_n,
self._param.similarity_threshold, 1 - self._param.keywords_similarity_weight,
aggs=False, rerank_mdl=rerank_mdl,
rank_feature=label_question(query, kbs))
# 如果检索结果为空(没有找到相关内容),则返回空响应
# 如果设置了 empty_response,则在响应中返回该内容。
if not kbinfos["chunks"]:
df = Retrieval.be_output("")
if self._param.empty_response and self._param.empty_response.strip():
df["empty_response"] = self._param.empty_response
return df
'''
如果有检索结果,将结果转换为 Pandas 的 DataFrame 格式。
将 content_with_weight 重命名为 content,然后删除 content_with_weight 列。
输出调试日志,记录查询和检索结果。
'''
df = pd.DataFrame(kbinfos["chunks"])
df["content"] = df["content_with_weight"]
del df["content_with_weight"]
logging.debug("{} {}".format(query, df))
return df
如果你需要一个 定制的代理组件,我可以基于这个
Retrieval
组件的架构,为你设计一个类似的组件,满足你的特定需求。你可以告诉我:
- 代理组件的具体用途(比如:请求转发、智能路由、流量控制、负载均衡、身份验证等)。
- 需要支持的参数(比如:代理地址、超时时间、认证方式等)。
- 核心功能(比如:日志记录、请求缓存、故障恢复等)。
- 集成的其他服务(比如:Redis、RabbitMQ、数据库等)。
如果你希望它遵循
ComponentBase
组件框架,我可以按照该模式来实现,让它可以与现有系统无缝对接!
下面的是引用的重要方法上一个代码块里的
retrieval.py
→ base.py
**kwargs 允许调用 _run() 方法时 传入任意数量的额外参数,即使这些参数没有在方法签名中显式列出#上方代码索引:#### check_decimal_float???
# check():对参数进行验证,确保【相似度阈值】和【权重】为有效的小数,且 top_n 是正数。
def check(self):
# "相似性阈值"
self.check_decimal_float(self.similarity_threshold, "[Retrieval] Similarity threshold")
# "关键词相似度权重"
self.check_decimal_float(self.keywords_similarity_weight, "[Retrieval] Keyword similarity weight")
self.check_positive_number(self.top_n, "[Retrieval] Top N")
--------------------------- ☆ 上方代码块片段 ☆ ---------------------------
# 校验检索相关参数的合法性
# 这是一个 静态方法,用于校验参数 param 是否是 0 到 1 之间的浮点数或整数
'''
校验逻辑:
param 必须是 float 或 int 类型(不允许 str、list 等)。
param 需要在 [0, 1] 之间,否则抛出 ValueError 异常,并在错误信息中包含 descr 描述。
'''
# 这里为什么是0-1 不能超过1? 如果 similarity_threshold = 1.2,会触发 check_decimal_float 的异常抛出,提示超出 [0,1] 范围。而且在前端的拖拉中也只会是0~1之间。因为这些参数代表 归一化(normalized)后的比例或权重,它们的值通常不能超过 1
# 【0表示示完全相同】
@staticmethod
def check_decimal_float(param, descr):
if type(param).__name__ not in ["float", "int"] or param < 0 or param > 1:
raise ValueError(
descr
+ " {} not supported, should be a float number in range [0, 1]".format(
param
)
)
'''
这里为什么要用 @staticmethod?
1. 避免创建对象,提高效率 👉 由于静态方法不依赖实体属性或方法,因此可以直接通过类调用,而不必创建对象
2. 代码组织清晰 👉 适用于逻辑独立的方法,可以让代码更容易读、更结构化
3. 防止修改实例状态 👉 由于静态方法不能访问self,因此不会修改实例的状态,保证了方法的纯函数特征
-------- -------- -------- 结合代码解析 -------- -------- --------
4. 这个方法 不访问实例变量,它只 检查参数 是否符合 0~1 之间的范围
5. 适用于 工具方法,属于 数据验证逻辑,不依赖 self
6. 可以直接通过类调用:
Retrieval.check_decimal_float(0.8, "Threshold") # 正常
Retrieval.check_decimal_float(1.2, "Threshold") # 抛出 ValueError
'''
==========================================================================
#上方代码索引:##### history, **kwargs???
def _run(self, history, **kwargs):
query = self.get_input()
query = str(query["content"][0]) if "content" in query else ""
......
# 讲解重点:【**kwargs】
# [**kwargs] 允许调用 _run() 方法时 传入任意数量的额外参数,即使这些参数没有在方法签名中显式列出 比如:👇
_run(self, history, user_id=123, debug=True, mode="fast")
# 在 _run() 方法内部,这些参数可以通过 kwargs["user_id"]、kwargs["debug"] 访问,但 当前代码里没有使用 kwargs,所以它只是用来兼容可能的扩展。
'''
目前 **kwargs 没有被使用,它的作用是:
扩展性:可以传递额外的参数而不修改函数签名
兼容性:允许不同调用方传递不同的参数,而 _run() 仍能正常运行
适用场景:未来需要额外参数
目前 **kwargs 只是 占位符,但它提供了未来扩展的可能性
'''
特征 | 普通方法(self) | 静态方法(@staticmethod) | 类方法(@classmethod) |
---|---|---|---|
依赖实例(self) | √ | × | × |
依赖类(cls) | × | × | √ |
访问实例变量 | √ | × | × |
访问类变量 | √ | × | √ |
适用于 | 需要访问实例 | 工具方法、验证方法 | 需要修改类变量 |
retrieval.py
→
让我们再来看一个案例
百度翻译:baidufanyi.py
路径 → agent/component/baidufanyi.py
#
import random
# 抽象基类模块 用于定义组件的基类
from abc import ABC
# 用于发送 HTTP 请求,与 百度翻译 API 交互
import requests
# 基础组件类,于构建此组件的父类,定义组件的基本结构和参数。
from agent.component.base import ComponentBase, ComponentParamBase
# 用于生成百度翻译 API 请求签名(加密哈希)
from hashlib import md5
# 百度翻译组件的参数定义类 继承ComponentParamBase
class BaiduFanyiParam(ComponentParamBase):
"""
Define the BaiduFanyi component parameters.
"""
def __init__(self):
super().__init__()
# appid 和 secret_key 是百度翻译 API 的 身份验证 信息(需要在百度翻译开放平台获取)
self.appid = "xxx"
self.secret_key = "xxx"
# translate:普通翻译 fieldtranslate:专业领域翻译
self.trans_type = 'translate'
self.parameters = []
# source_lang 翻译的源语言 target_lang 翻译的目标语言
self.source_lang = 'auto'
self.target_lang = 'auto'
# domain 如果使用专业领域翻译,需指定领域(如 finance 表示 金融 领域)
self.domain = 'finance'
# 检查参数合法性
def check(self):
# check_empty():确保 appid 和 secret_key 不能为空。
self.check_empty(self.appid, "BaiduFanyi APPID")
self.check_empty(self.secret_key, "BaiduFanyi Secret Key")
# check_valid_value():确保 trans_type、source_lang、target_lang、domain 的值在合法选项之内
self.check_valid_value(self.trans_type, "Translate type", ['translate', 'fieldtranslate'])
# 以下是列举各种语言
self.check_valid_value(self.source_lang, "Source language",
['auto', 'zh', 'en', 'yue', 'wyw', 'jp', 'kor', 'fra', 'spa', 'th', 'ara', 'ru', 'pt',
'de', 'it', 'el', 'nl', 'pl', 'bul', 'est', 'dan', 'fin', 'cs', 'rom', 'slo', 'swe',
'hu', 'cht', 'vie'])
self.check_valid_value(self.target_lang, "Target language",
['auto', 'zh', 'en', 'yue', 'wyw', 'jp', 'kor', 'fra', 'spa', 'th', 'ara', 'ru', 'pt',
'de', 'it', 'el', 'nl', 'pl', 'bul', 'est', 'dan', 'fin', 'cs', 'rom', 'slo', 'swe',
'hu', 'cht', 'vie'])
# 以下是列举不同专业领域
self.check_valid_value(self.domain, "Translate field",
['it', 'finance', 'machinery', 'senimed', 'novel', 'academic', 'aerospace', 'wiki',
'news', 'law', 'contract'])
# 继承ComponentBase和ABC
class BaiduFanyi(ComponentBase, ABC):
# 定义组件名称在系统内唯一标识该组件
component_name = "BaiduFanyi"
# _run()是组件的 核心执行函数,用于处理翻译请求。
# **kwargs:可以让函数更加灵活,因为它可以接受任意数量的命名参数
# 在函数内部,kwargs 是一个字典,包含了所有传递给函数的额外命名参数。
def _run(self, history, **kwargs):
'''
self.get_input() 获取输入内容。
如果 content 存在,则用 - 连接多个内容(拼接成单个字符串)。
如果 ans 为空,直接返回 ""
'''
ans = self.get_input()
ans = " - ".join(ans["content"]) if "content" in ans else ""
if not ans:
return BaiduFanyi.be_output("")
try:
'''
source_lang 和 target_lang:来源和目标语言。
appid:百度翻译 API 的 应用 ID。
salt:随机数(百度 API 需要此参数)。
secret_key:百度 API 的 密钥
'''
source_lang = self._param.source_lang
target_lang = self._param.target_lang
appid = self._param.appid
salt = random.randint(32768, 65536)
secret_key = self._param.secret_key
-------------------------- ☆ 普通翻译API请求 ☆ --------------------------
if self._param.trans_type == 'translate':
# md5签名,防止请求被篡改
sign = md5((appid + ans + salt + secret_key).encode('utf-8')).hexdigest()
# 发送 HTTP POST 请求 访问 百度翻译 API
url = 'http://api.fanyi.baidu.com/api/trans/vip/translate?' + 'q=' + ans + '&from=' + source_lang + '&to=' + target_lang + '&appid=' + appid + '&salt=' + salt + '&sign=' + sign
headers = {"Content-Type": "application/x-www-form-urlencoded"}
# 解析返回结果
response = requests.post(url=url, headers=headers).json()
if response.get('error_code'):
BaiduFanyi.be_output("**Error**:" + response['error_msg'])
return BaiduFanyi.be_output(response['trans_result'][0]['dst'])
-------------------------- ★ 专业翻译API请求 ★ --------------------------
elif self._param.trans_type == 'fieldtranslate':
domain = self._param.domain
sign = md5((appid + ans + salt + domain + secret_key).encode('utf-8')).hexdigest()
url = 'http://api.fanyi.baidu.com/api/trans/vip/fieldtranslate?' + 'q=' + ans + '&from=' + source_lang + '&to=' + target_lang + '&appid=' + appid + '&salt=' + salt + '&domain=' + domain + '&sign=' + sign
headers = {"Content-Type": "application/x-www-form-urlencoded"}
response = requests.post(url=url, headers=headers).json()
if response.get('error_code'):
BaiduFanyi.be_output("**Error**:" + response['error_msg'])
return BaiduFanyi.be_output(response['trans_result'][0]['dst'])
# 捕获 所有异常,防止程序崩溃
except Exception as e:
BaiduFanyi.be_output("**Error**:" + str(e))
深层次研究
from agent.component.base import ComponentBase, ComponentParamBase
主要用于处理、更新、验证和警告组件参数,适用于那些需要动态配置、递归嵌套结构且要求高可扩展性的系统
class ComponentParamBase(ABC):
def __init__(self):
# 初始化输出变量名
self.output_var_name = "output"
# 初始化消息历史窗口大小
self.message_history_window_size = 22
# 初始化查询参数
self.query = []
# 初始化输入参数
self.inputs = []
# 初始化调试输入参数
self.debug_inputs = []
# 设置组件的名称,并返回当前对象,支持链式调用
def set_name(self, name: str):
self._name = name
return self
# 抽象方法 子类必须实现 它的目的是检查参数是否有效
def check(self):
raise NotImplementedError("Parameter Object should be checked.")
# 类方法 用于检查类是否具有某个属性 _DEPRECATED_PARAMS 如果没有 则初始化一个空的集合
@classmethod
def _get_or_init_deprecated_params_set(cls):
if not hasattr(cls, _DEPRECATED_PARAMS):
setattr(cls, _DEPRECATED_PARAMS, set())
return getattr(cls, _DEPRECATED_PARAMS)
# 用于检查和初始化实例的已废弃参数集合
def _get_or_init_feeded_deprecated_params_set(self, conf=None):
if not hasattr(self, _FEEDED_DEPRECATED_PARAMS):
if conf is None:
setattr(self, _FEEDED_DEPRECATED_PARAMS, set())
else:
setattr(
self,
_FEEDED_DEPRECATED_PARAMS,
set(conf[_FEEDED_DEPRECATED_PARAMS]),
)
return getattr(self, _FEEDED_DEPRECATED_PARAMS)
# 用于检查和初始化实例的已废弃参数集合
def _get_or_init_user_feeded_params_set(self, conf=None):
if not hasattr(self, _USER_FEEDED_PARAMS):
if conf is None:
setattr(self, _USER_FEEDED_PARAMS, set())
else:
setattr(self, _USER_FEEDED_PARAMS, set(conf[_USER_FEEDED_PARAMS]))
return getattr(self, _USER_FEEDED_PARAMS)
# 返回用户提供的参数
def get_user_feeded(self):
return self._get_or_init_user_feeded_params_set()
# 返回已废弃的参数集合
def get_feeded_deprecated_params(self):
return self._get_or_init_feeded_deprecated_params_set()
# @装饰器 定义了 _deprecated_params_set 属性 它返回已废弃的参数集合
@property
def _deprecated_params_set(self):
return {name: True for name in self.get_feeded_deprecated_params()}
# 将对象转换为 JSON 字符串
def __str__(self):
return json.dumps(self.as_dict(), ensure_ascii=False)
# 递归地将对象的属性转换成字典 适用于将对象序列化为 JSON 或进行其他操作
def as_dict(self):
def _recursive_convert_obj_to_dict(obj):
ret_dict = {}
for attr_name in list(obj.__dict__):
if attr_name in [_FEEDED_DEPRECATED_PARAMS, _DEPRECATED_PARAMS, _USER_FEEDED_PARAMS, _IS_RAW_CONF]:
continue
# get attr
attr = getattr(obj, attr_name)
if isinstance(attr, pd.DataFrame):
ret_dict[attr_name] = attr.to_dict()
continue
if attr and type(attr).__name__ not in dir(builtins):
ret_dict[attr_name] = _recursive_convert_obj_to_dict(attr)
else:
ret_dict[attr_name] = attr
return ret_dict
return _recursive_convert_obj_to_dict(self)
# 用于更新对象的参数,并根据传入的配置进行修改 它处理递归更新参数、检查冗余属性等
def update(self, conf, allow_redundant=False):
update_from_raw_conf = conf.get(_IS_RAW_CONF, True)
if update_from_raw_conf:
deprecated_params_set = self._get_or_init_deprecated_params_set()
feeded_deprecated_params_set = (
self._get_or_init_feeded_deprecated_params_set()
)
user_feeded_params_set = self._get_or_init_user_feeded_params_set()
setattr(self, _IS_RAW_CONF, False)
else:
feeded_deprecated_params_set = (
self._get_or_init_feeded_deprecated_params_set(conf)
)
user_feeded_params_set = self._get_or_init_user_feeded_params_set(conf)
def _recursive_update_param(param, config, depth, prefix):
if depth > settings.PARAM_MAXDEPTH:
raise ValueError("Param define nesting too deep!!!, can not parse it")
inst_variables = param.__dict__
redundant_attrs = []
for config_key, config_value in config.items():
# redundant attr
if config_key not in inst_variables:
if not update_from_raw_conf and config_key.startswith("_"):
setattr(param, config_key, config_value)
else:
setattr(param, config_key, config_value)
# redundant_attrs.append(config_key)
continue
full_config_key = f"{prefix}{config_key}"
if update_from_raw_conf:
# add user feeded params
user_feeded_params_set.add(full_config_key)
# update user feeded deprecated param set
if full_config_key in deprecated_params_set:
feeded_deprecated_params_set.add(full_config_key)
# supported attr
attr = getattr(param, config_key)
if type(attr).__name__ in dir(builtins) or attr is None:
setattr(param, config_key, config_value)
else:
# recursive set obj attr
sub_params = _recursive_update_param(
attr, config_value, depth + 1, prefix=f"{prefix}{config_key}."
)
setattr(param, config_key, sub_params)
if not allow_redundant and redundant_attrs:
raise ValueError(
f"cpn `{getattr(self, '_name', type(self))}` has redundant parameters: `{[redundant_attrs]}`"
)
return param
return _recursive_update_param(param=self, config=conf, depth=0, prefix="")
# 用于提取所有非内置类型的属性 递归地遍历对象的属性
def extract_not_builtin(self):
def _get_not_builtin_types(obj):
ret_dict = {}
for variable in obj.__dict__:
attr = getattr(obj, variable)
if attr and type(attr).__name__ not in dir(builtins):
ret_dict[variable] = _get_not_builtin_types(attr)
return ret_dict
return _get_not_builtin_types(self)
# validate 方法用于验证对象的参数是否符合预定义的规则 规则存储在 JSON 文件中
def validate(self):
self.builtin_types = dir(builtins)
self.func = {
"ge": self._greater_equal_than,
"le": self._less_equal_than,
"in": self._in,
"not_in": self._not_in,
"range": self._range,
}
home_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
param_validation_path_prefix = home_dir + "/param_validation/"
param_name = type(self).__name__
param_validation_path = "/".join(
[param_validation_path_prefix, param_name + ".json"]
)
validation_json = None
try:
with open(param_validation_path, "r") as fin:
validation_json = json.loads(fin.read())
except BaseException:
return
self._validate_param(self, validation_json)
def _validate_param(self, param_obj, validation_json):
default_section = type(param_obj).__name__
var_list = param_obj.__dict__
for variable in var_list:
attr = getattr(param_obj, variable)
if type(attr).__name__ in self.builtin_types or attr is None:
if variable not in validation_json:
continue
validation_dict = validation_json[default_section][variable]
value = getattr(param_obj, variable)
value_legal = False
for op_type in validation_dict:
if self.func[op_type](value, validation_dict[op_type]):
value_legal = True
break
if not value_legal:
raise ValueError(
"Plase check runtime conf, {} = {} does not match user-parameter restriction".format(
variable, value
)
)
elif variable in validation_json:
self._validate_param(attr, validation_json)
# 用于验证参数的类型和范围 确保符合预期
@staticmethod
def check_string(param, descr):
if type(param).__name__ not in ["str"]:
raise ValueError(
descr + " {} not supported, should be string type".format(param)
)
@staticmethod
def check_empty(param, descr):
if not param:
raise ValueError(
descr + " does not support empty value."
)
@staticmethod
def check_positive_integer(param, descr):
if type(param).__name__ not in ["int", "long"] or param <= 0:
raise ValueError(
descr + " {} not supported, should be positive integer".format(param)
)
@staticmethod
def check_positive_number(param, descr):
if type(param).__name__ not in ["float", "int", "long"] or param <= 0:
raise ValueError(
descr + " {} not supported, should be positive numeric".format(param)
)
@staticmethod
def check_nonnegative_number(param, descr):
if type(param).__name__ not in ["float", "int", "long"] or param < 0:
raise ValueError(
descr
+ " {} not supported, should be non-negative numeric".format(param)
)
@staticmethod
def check_decimal_float(param, descr):
if type(param).__name__ not in ["float", "int"] or param < 0 or param > 1:
raise ValueError(
descr
+ " {} not supported, should be a float number in range [0, 1]".format(
param
)
)
@staticmethod
def check_boolean(param, descr):
if type(param).__name__ != "bool":
raise ValueError(
descr + " {} not supported, should be bool type".format(param)
)
@staticmethod
def check_open_unit_interval(param, descr):
if type(param).__name__ not in ["float"] or param <= 0 or param >= 1:
raise ValueError(
descr + " should be a numeric number between 0 and 1 exclusively"
)
@staticmethod
def check_valid_value(param, descr, valid_values):
if param not in valid_values:
raise ValueError(
descr
+ " {} is not supported, it should be in {}".format(param, valid_values)
)
@staticmethod
def check_defined_type(param, descr, types):
if type(param).__name__ not in types:
raise ValueError(
descr + " {} not supported, should be one of {}".format(param, types)
)
@staticmethod
def check_and_change_lower(param, valid_list, descr=""):
if type(param).__name__ != "str":
raise ValueError(
descr
+ " {} not supported, should be one of {}".format(param, valid_list)
)
lower_param = param.lower()
if lower_param in valid_list:
return lower_param
else:
raise ValueError(
descr
+ " {} not supported, should be one of {}".format(param, valid_list)
)
@staticmethod
def _greater_equal_than(value, limit):
return value >= limit - settings.FLOAT_ZERO
@staticmethod
def _less_equal_than(value, limit):
return value <= limit + settings.FLOAT_ZERO
@staticmethod
def _range(value, ranges):
in_range = False
for left_limit, right_limit in ranges:
if (
left_limit - settings.FLOAT_ZERO
<= value
<= right_limit + settings.FLOAT_ZERO
):
in_range = True
break
return in_range
@staticmethod
def _in(value, right_value_list):
return value in right_value_list
@staticmethod
def _not_in(value, wrong_value_list):
return value not in wrong_value_list
def _warn_deprecated_param(self, param_name, descr):
if self._deprecated_params_set.get(param_name):
logging.warning(
f"{descr} {param_name} is deprecated and ignored in this version."
)
def _warn_to_deprecate_param(self, param_name, descr, new_param):
if self._deprecated_params_set.get(param_name):
logging.warning(
f"{descr} {param_name} will be deprecated in future release; "
f"please use {new_param} instead."
)
return True
return False
这里运用到json 为什么要用json?
- 方便序列化:
json.dumps()
将对象转换为JSON字符串,可以轻松地保存或传输对象数据。 - 方便调试和展示:在
__str__
方法中返回 JSON 字符串,可以方便地查看对象的内容。尤其是在调试过程中,直接打印出对象的 JSON 格式可以帮助开发人员快速查看对象的状态及其内部数据结构 - 与大模型交互:当涉及到与大语言模型(如 GPT)或其他机器学习模型的交互时,JSON 格式的数据通常是标准的输入和输出格式。将对象转为 JSON 字符串,能够更容易地将数据传递给模型进行处理或分析,模型通常会接受 JSON 格式的数据进行训练或推理。
如何去运用?
- 数据交换:可以将这个 JSON 字符串用作 API 请求或响应的数据格式。例如,你可以将这个对象作为 HTTP 请求的 body 发送,或者从网络中获取 JSON 格式的数据,然后解析回对象。
- 持久化存储:如果你需要将对象数据持久化到数据库或文件系统,JSON 是一个很好的存储格式。例如,将对象数据存储在文件中或数据库表的 JSON 类型字段中,便于未来读取和操作。
- 配置和参数更新:
update
方法中接收到的conf
参数实际上是一个字典对象,它通过递归的方式更新对象的属性。如果你将对象序列化为 JSON 格式后,可以将 JSON 作为配置文件传递给应用,应用根据该配置动态调整其行为。这使得系统更加灵活和可配置。 - 验证与校验:
validate
方法利用存储在 JSON 文件中的规则对对象进行参数验证,这是一种常见的方式来确保输入的数据符合特定的格式或限制。可以通过动态加载配置文件来验证对象的数据,保证数据的合法性。
怎么和大模型交互 json是以什么规范 是否让机器更好的理解?
如果自己开发了一个代理组件后如何去融入呢?
index.tsx
路径web/src/pages/flow/form/baidu-fanyi-form/index.tsx
BaiduFanyiForm
是一个用于配置百度翻译(Baidu Fanyi)接口参数的表单组件。该组件基于 Ant Design (antd
) 的 Form
组件,允许用户输入翻译所需的 AppID、密钥、翻译类型、领域、源语言和目标语言。
// useTranslate('flow'):自定义 Hook,提供国际化翻译能力
import { useTranslate } from '@/hooks/common-hooks';
// Form, Input, Select:Ant Design 表单组件
import { Form, Input, Select } from 'antd';
// useMemo:优化计算、提高性能,避免不必要的重新计算
import { useMemo } from 'react';
// 从 constant 中引入翻译领域和语言选项
import {
BaiduFanyiDomainOptions,
BaiduFanyiSourceLangOptions,
} from '../../constant';
// 表单组件的类型定义,包含 onValuesChange, form, node 等参数
import { IOperatorForm } from '../../interface';
// 动态输入组件,可能用于变量替换
import DynamicInputVariable from '../components/dynamic-input-variable';
/*
生成 trans_type(翻译类型)的 Select 选项:
translate:普通翻译
fieldtranslate:专业领域翻译
选项的 label 通过 t() 进行国际化翻译。
*/
const BaiduFanyiForm = ({ onValuesChange, form, node }: IOperatorForm) => {
const { t } = useTranslate('flow');
const options = useMemo(() => {
return ['translate', 'fieldtranslate'].map((x) => ({
value: x,
label: t(`baiduSecretKeyOptions.${x}`),
}));
}, [t]);
// 生成 领域翻译 选项(如医学、法律等)
const baiduFanyiOptions = useMemo(() => {
return BaiduFanyiDomainOptions.map((x) => ({
value: x,
label: t(`baiduDomainOptions.${x}`),
}));
}, [t]);
// 源语言(source_lang)和 目标语言(target_lang)
const baiduFanyiSourceLangOptions = useMemo(() => {
return BaiduFanyiSourceLangOptions.map((x) => ({
value: x,
label: t(`baiduSourceLangOptions.${x}`),
}));
}, [t]);
return (
<Form
name="basic" //表单名称
autoComplete="off" //关闭自动填充
form={form} //绑定 Ant Design 表单实例,允许动态设置表单值
onValuesChange={onValuesChange} //表单值变更时触发外部回调
layout={'vertical'}
>
<!-- 允许用户输入 AppID 和 密钥,用于身份验证 -->
<DynamicInputVariable node={node}></DynamicInputVariable>
<Form.Item label={t('appid')} name={'appid'}>
<Input></Input>
</Form.Item>
<Form.Item label={t('secretKey')} name={'secret_key'}>
<Input></Input>
</Form.Item>
<!-- 翻译类型 -->
<Form.Item label={t('transType')} name={'trans_type'}>
<Select options={options}></Select>
</Form.Item>
<!-- 领域翻译 仅在 fieldtranslate 选中时显示 -->
<!-- dependencies={['model_type']}:监听 model_type 字段的变化 -->
<Form.Item noStyle dependencies={['model_type']}>
{({ getFieldValue }) =>
getFieldValue('trans_type') === 'fieldtranslate' && (
<Form.Item label={t('domain')} name={'domain'}>
<Select options={baiduFanyiOptions}></Select>
</Form.Item>
)
}
</Form.Item>
<Form.Item label={t('sourceLang')} name={'source_lang'}>
<Select options={baiduFanyiSourceLangOptions}></Select>
</Form.Item>
<Form.Item label={t('targetLang')} name={'target_lang'}>
<Select options={baiduFanyiSourceLangOptions}></Select>
</Form.Item>
</Form>
);
};
export default BaiduFanyiForm;
BaiduFanyiForm
组件是百度翻译 API 参数的前端配置表单,支持动态交互(如 trans_type
选择 fieldtranslate
时动态显示 domain
选项)。该组件用于某个流程(flow)系统,允许用户输入翻译 API 相关信息,并提供 国际化支持(useTranslate()
)
_ _ init _ _.py
首先在 agent/component/__init__.py
在组件同级目录下有一个__init__.py
它的主要作用是**导入并管理各种组件,这些组件用于数据处理、信息检索、翻译、SQL执行、财经数据获取等**不同功能。其中的重要代码:
# import importlib:用于动态导入模块(在 component_class 方法中使用)
# 这些组件分布在 agent.component 模块下,提供不同的功能
'''
该函数用于动态加载组件,通过 importlib.import_module("agent.component") 导入 agent.component 模块,然后使用 getattr(m, class_name) 获取类,从而返回对应的组件类。
'''
def component_class(class_name):
m = importlib.import_module("agent.component")
c = getattr(m, class_name)
return c
'''
__all__ 列表:指定模块对外暴露的所有组件
使其成为 from module import * 时可以被访问的对象。
'''
__all__ = [
"GitHub",
"GitHubParam",
"BaiduFanyi",
"BaiduFanyiParam",
]
web/src/pages/flow/flow-drawer/index.tsx
在web/src/pages/flow/flow-drawer/index.tsx
里有这样一行代码import BaiduFanyiForm from '../form/baidu-fanyi-form';
该文件 index.tsx
主要用于**表单抽屉组件 (FormDrawer
)**,用于在 UI 中展示不同操作节点的表单,并支持编辑、调试等功能。
代码功能概览
🔹 动态渲染不同的表单组件(BaiduFanyiForm
等)
🔹 支持不同类型的操作节点(Operator)(如 BaiduFanyi
, Google
, Generate
等)
🔹 提供表单输入交互,并支持单步调试
🔹 使用 Drawer
组件作为侧边栏弹出窗口
★ 组件导入
import { Drawer, Flex, Form, Input } from 'antd';
import { get, isPlainObject, lowerFirst } from 'lodash';
import { Play } from 'lucide-react';
import { CloseOutlined } from '@ant-design/icons';
...
📌 作用:引入 Ant Design 组件(Drawer
、Form
、Input
)、lodash
工具库以及 Play
(调试按钮)、CloseOutlined
(关闭按钮)。
★ 引入表单组件
# 当然不能忘了我们的百度翻译
import BaiduFanyiForm from '../form/baidu-fanyi-form';
import GoogleForm from '../form/google-form';
import GenerateForm from '../form/generate-form';
import RetrievalForm from '../form/retrieval-form';
...
📌 作用:引入 BaiduFanyiForm
组件,该组件用于百度翻译的表单交互。
✅ 这些表单组件用于不同的 Operator
操作,例如:
BaiduFanyiForm
→ 百度翻译表单GoogleForm
→ Google 搜索表单GenerateForm
→ 文本生成表单RetrievalForm
→ 信息检索表单
★ FormMap(操作类型与表6单组件的映射)
const FormMap = {
[Operator.BaiduFanyi]: BaiduFanyiForm,
[Operator.Google]: GoogleForm,
[Operator.Generate]: GenerateForm,
[Operator.Retrieval]: RetrievalForm,
};
📌 作用:
✅ 动态匹配 Operator
(操作类型)与 对应表单组件
✅ 当 Operator
为 BaiduFanyi
时,渲染 BaiduFanyiForm
✅ 可以扩展不同的表单类型
☆ FormDrawer组件
const FormDrawer = ({ visible, hideModal, node, singleDebugDrawerVisible, hideSingleDebugDrawer, showSingleDebugDrawer }: IModalProps<any> & IProps) => {
📌 作用:FormDrawer
是一个 表单抽屉组件,用于展示不同类型的表单。
☆ 动态渲染表单
const OperatorForm = FormMap[operatorName] ?? EmptyContent;
📌 作用: ✅ 根据 Operator
(操作类型)选择对应的表单组件
✅ 如果 Operator
不在 FormMap
中,则显示 EmptyContent
(空组件)
☆ 处理表单数据
useEffect(() => {
if (visible) {
form.resetFields();
form.setFieldsValue(node?.data?.form);
}
}, [visible, form, node?.data?.form]);
📌 作用: ✅ 当抽屉可见时,重置表单并填充数据
✅ 如果 Operator
是 Categorize
,则特殊处理分类数据
☆ 侧边栏 (Drawer
)
<Drawer
title={
<Flex>
<OperatorIcon name={operatorName} />
<Input value={name} onChange={handleNameChange} />
<Play onClick={showSingleDebugDrawer} />
<CloseOutlined onClick={hideModal} />
</Flex>
}
open={visible}
>
<OperatorForm onValuesChange={handleValuesChange} form={form} node={node} />
</Drawer>
📌 作用: ✅ 标题区域包含操作图标、输入框、调试按钮、关闭按钮
✅ 使用 OperatorForm
组件渲染表单
✅ 点击 Play
按钮打开调试模式
★ ★ 总结一下BaiduFanyiForm
相关逻辑 ★ ★
在 index.tsx
里,BaiduFanyiForm
被注册到 FormMap
,然后通过 FormDrawer
组件
🚀 如何渲染 BaiduFanyiForm
?
Operator.BaiduFanyi
触发BaiduFanyiForm
FormDrawer
组件动态匹配FormMap[Operator.BaiduFanyi]
BaiduFanyiForm
渲染在Drawer
中
📌 结论: ✅ BaiduFanyiForm
只是众多表单组件之一,专门用于百度翻译的功能
✅ index.tsx
主要是表单的动态加载器,统一管理所有 Operator
类型
✅ FormDrawer
负责 UI 渲染、数据填充、调试交互等
① agent/component/baidufanyi.py 【后端代码】
② agent/component/_ _ init _ _.py 【初始化】
③ web/src/pages/flow/flow-drawer/index.tsx【表单抽屉组件 UI展示】
④ web/src/pages/flow/form/baidu-fanyi-form/index.tsx 【提供用户界面让用户能配置并提交参数】
⑤ web/src/pages/flow/constant.tsx 【定义一个应用程序中使用的各种图标、常量、枚举、接口、状态和函数】
⑥ web/src/locales/zh.ts 【把所有要显示的搞到zh.ts中(前端组件描述)】
⑦ web/src/pages/agent/constant.tsx【引入组件所需的各种图标、以及操作项的样式、初始值、语言等】
⑧ web/src/pages/agent/form-sheet/use-form-config-map.tsx 【表单配置映射、配置不同操作对应表单组件】
⑨ web/src/pages/agent/form/baidu-fanyi-form/index.tsx 【构建配置表单 】
⑩ web/src/pages/agent/hooks.tsx 【管理图形界面流程图 各种钩子 → 建数据处理流程】